package com.assistant.system;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Shell32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;


/**
 * @author Anyang
 * @version 1.0.0
 * @date 2025/10/25 05:10:03
 * @description 系统工具类，提供与操作系统交互的实用方法：封装了打开应用、打开 URL、模拟键盘输入等底层操作。
 */
public class SystemUtil {
    private static final Logger logger = LoggerFactory.getLogger(SystemUtil.class);
    private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("win");
    // 启动应用的窗口等待超时时间（毫秒）
    private static final long APP_STARTUP_TIMEOUT_MS = 5000;

    /**
     * 将中文应用名转换为系统启动名或 URI 协议名。
     * @param appName 用户语音指令中提取的应用名称
     * @return 实际用于系统启动的命令或协议名
     */
    private static String mapAppName(String appName) {
        String lowerCaseName = appName.trim().toLowerCase();

        // 【修复 1】步骤 1: 移除常见的口语修饰词，简化核心名称
        String coreName = lowerCaseName
                .replace("我的", "")
                .replace("电脑里的", "")
                .replace("打开", "")
                .trim();

        // 步骤 2: 基于简化后的核心名称进行匹配
        switch (coreName) {
            case "微信":
                return "weixin://";
            case "qq":
                return "tencent://";
            case "记事本":
                return "notepad.exe";
            case "浏览器":
            case "chrome":
                return "chrome.exe";
            case "vscode":
                return "code";
            // 【新增】支持网易云音乐 URI
            case "网易云音乐":
            case "音乐":
            case "网易云":
                return "orpheus://";
            default:
                return coreName;
        }
    }

    /**
     * 【OpenAppCommand 统一调用入口】
     * 使用 JNA 调用 ShellExecute 启动应用，并使用 EnumWindows 验证启动（MCP 风格）。
     * @param targetApp 要启动的应用的名称。
     * @return 如果应用被系统接受启动，则返回 true。
     */
    public static boolean openApplication(String targetApp) {
        if (!IS_WINDOWS) {
            // 非 Windows 系统使用 ProcessBuilder 的兼容启动方式
            return fallbackOpenApplication(targetApp);
        }

        String systemAppName = mapAppName(targetApp);
        logger.info("【系统调用】尝试通过 ShellExecute 启动应用: {}", systemAppName);

        // 1. 调用 ShellExecute 进行启动
        int result = Shell32.INSTANCE.ShellExecute(
                null,
                "open",
                systemAppName,
                null,
                null,
                User32.SW_SHOWNORMAL
        ).intValue();

        if (result <= 32) {
            logger.error("【启动失败】ShellExecute 启动应用 {} 失败，返回码: {}", systemAppName, result);
            return false;
        }

        logger.info("【启动尝试】ShellExecute 启动指令发送成功。返回码: {}", result);

        // 【修复 2】关键修复：针对 URI 协议，跳过不可靠的窗口验证。
        if (systemAppName.contains("://")) {
            logger.info("【验证成功】URI 协议启动指令发送成功。跳过窗口验证。");
            return true;
        }

        // 2. 原来的窗口查找逻辑只对普通的可执行文件保留
        // 【修改】传入 targetApp (用户说的名字) 用于验证
        boolean windowFound = isAppWindowPresent(targetApp, APP_STARTUP_TIMEOUT_MS);

        if (windowFound) {
            logger.info("【验证成功】应用【{}】的窗口已在 {}ms 内被找到。", targetApp, APP_STARTUP_TIMEOUT_MS);
            return true;
        } else {
            logger.warn("【验证失败】应用【{}】启动指令已发送，但在 {}ms 内未能找到匹配的窗口。", targetApp, APP_STARTUP_TIMEOUT_MS);
            // 严格执行：窗口找不到，判定为失败，避免虚假成功。
            // 【修改】这里改为 true。ShellExecute > 32 意味着系统已接受指令。
            // 窗口验证失败不应覆盖启动成功的事实，它可能只是启动慢或窗口标题不匹配。
            logger.warn("【验证放行】窗口验证失败，但 ShellExecute 已成功 (Code > 32)，假定启动成功。");
            return true;
        }
    }

    /**
     * 【核心】使用 JNA EnumWindows 实现窗口句柄查找（MCP风格）。
     * @param appName 用户应用名称（用于匹配窗口标题）
     * @param timeoutMs 等待超时时间
     * @return 如果在超时时间内找到匹配窗口，则返回 true
     */
    public static boolean isAppWindowPresent(String appName, long timeoutMs) {
        long startTime = System.currentTimeMillis();

        final String lowerCaseAppName = appName.toLowerCase();
        final String validationKey;
        final String validationClass;

        switch (lowerCaseAppName) {
            case "浏览器":
            case "chrome":
                validationKey = "chrome";
                validationClass = null;
                break;
            case "记事本":
                validationKey = "notepad";
                validationClass = null;
                break;
            case "qq":
                validationKey = "qq";
                validationClass = null;
                break;
            case "微信":
                validationKey = "微信";
                validationClass = null;
                break;
            case "vscode":
                validationKey = "visual studio code";
                validationClass = null;
                break;

            case "网易云音乐":
            case "音乐":
            case "网易云":
                validationKey = null; // 标题是动态歌曲名，不使用
                validationClass = "OrpheusWidget"; // 使用固定的窗口类名
                break;

            default:
                validationKey = lowerCaseAppName; // 默认使用用户口述词
                validationClass = null;
        }

        logger.debug("【窗口验证】开始查找并激活窗口，AppName: {}, ValidationKey: {}, ValidationClass: {}",
                appName, validationKey, validationClass);

        while (System.currentTimeMillis() - startTime < timeoutMs) {
            final boolean[] found = {false};
            User32.INSTANCE.EnumWindows(new WNDENUMPROC() {
                @Override
                public boolean callback(HWND hWnd, com.sun.jna.Pointer data) {
                    if (User32.INSTANCE.IsWindowVisible(hWnd)) {

                        // --- 策略 1：按类名 (Class Name) 查找 ---
                        if (validationClass != null) {
                            char[] classNameChars = new char[512];
                            User32.INSTANCE.GetClassName(hWnd, classNameChars, 512);
                            String className = Native.toString(classNameChars).trim();

                            // 【【【 核心修复 1: 使用 equalsIgnoreCase 匹配类名 】】】
                            if (className.equalsIgnoreCase(validationClass)) {
                                logger.debug("【窗口验证】找到匹配窗口 (By Class): {}", className);
                                // 【【【 核心修复 2: 激活窗口 】】】
                                activateWindow(hWnd);
                                found[0] = true;
                                return false; // 找到，停止枚举
                            }
                        }

                        // --- 策略 2：按标题 (Title) 查找 ---
                        if (validationKey != null) {
                            char[] windowTitle = new char[512];
                            User32.INSTANCE.GetWindowText(hWnd, windowTitle, 512);
                            String title = new String(windowTitle).trim().toLowerCase();

                            if (title.contains(validationKey)) {
                                if (!title.contains("cmd.exe") && !title.contains("process")) {
                                    logger.debug("【窗口验证】找到匹配窗口 (By Title): {}", new String(windowTitle).trim());
                                    // 【【【 核心修复 2: 激活窗口 】】】
                                    activateWindow(hWnd);
                                    found[0] = true;
                                    return false; // 找到，停止枚举
                                }
                            }
                        }
                    }
                    return true; // 继续枚举
                }
            }, null);

            if (found[0]) {
                return true; // 成功找到并激活
            }

            try {
                Thread.sleep(200); // 避免 CPU 占用过高
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }

        logger.debug("【窗口验证】超时 {}ms，未找到 Key: {} 或 Class: {}", timeoutMs, validationKey, validationClass);
        return false;
    }

    /**
     * 【【【 新增辅助方法：激活窗口 】】】
     * 将指定句柄的窗口恢复并置于前台。
     * @param hWnd 窗口句柄
     */
    private static void activateWindow(HWND hWnd) {
        // 恢复窗口（如果它被最小化了）
        User32.INSTANCE.ShowWindow(hWnd, User32.SW_RESTORE);
        // 将窗口带到前台
        User32.INSTANCE.SetForegroundWindow(hWnd);
        logger.info("【窗口激活】已尝试将句柄 {} 置于前台。", hWnd.getPointer());
    }

    /**
     * 非 Windows 系统下的应用启动兼容方案（使用 ProcessBuilder）。
     */
    private static boolean fallbackOpenApplication(String appName) {
        String targetName = mapAppName(appName);
        String os = System.getProperty("os.name").toLowerCase();
        List<String> command;

        if (targetName.contains("://")) {
            // 非 Windows 下 URI 协议启动
            return openUrl(targetName);
        }

        if (os.contains("mac")) {
            command = Arrays.asList("open", "-a", targetName);
        } else if (os.contains("linux")) {
            command = Arrays.asList("xdg-open", targetName);
        } else {
            logger.error("【启动失败】不支持的操作系统: {}", os);
            return false;
        }

        try {
            ProcessBuilder builder = new ProcessBuilder(command);
            builder.start();
            Thread.sleep(500); // 给予系统启动时间
            logger.info("成功尝试执行启动命令: {}", String.join(" ", command));
            return true;
        } catch (IOException e) {
            logger.error("启动应用失败，可能是应用协议或可执行文件名不正确：{}", e.getMessage());
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    /**
     * 使用系统默认方式打开一个 URL。
     * @param url 要打开的网址
     * @return 操作是否成功
     */
    public static boolean openUrl(String url) {
        // 1. 确保 URL 有 scheme，如果没有，默认
        String finalUrl = url.contains("://") ? url : "https://" + url;

        // 2. 【核心修复】对于 Windows，使用 ShellExecute，
        // 它能正确处理 http://, https://, 和 orpheus:// 等自定义协议，
        // 且不会因 java.net.URI 解析失败而崩溃。
        if (IS_WINDOWS) {
            try {
                int result = Shell32.INSTANCE.ShellExecute(
                        null,
                        "open",
                        finalUrl, // 直接传递 URL 或 URI
                        null,
                        null,
                        User32.SW_SHOWNORMAL
                ).intValue();

                if (result <= 32) {
                    logger.error("【ShellExecute 失败】打开 URL {} 失败，返回码: {}", finalUrl, result);
                    return false;
                }
                logger.info("【ShellExecute 成功】URL 成功打开: {}", finalUrl);
                return true;
            } catch (Exception e) {
                // 捕获 JNA 可能抛出的异常
                logger.error("【ShellExecute 异常】打开 URL {} 失败: {}", finalUrl, e.getMessage(), e);
                return false;
            }
        } else {
            // 3. 对于非 Windows (Mac/Linux)，保留原有的 Desktop.browse 逻辑
            if (!Desktop.isDesktopSupported() || !Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
                logger.error("操作系统不支持打开浏览器操作。");
                return false;
            }
            try {
                // 这里的 new URI() 仍然可能在 Mac/Linux 上对 orpheus:// 失败，
                // 但这是针对 Windows 用户的修复。
                Desktop.getDesktop().browse(new URI(finalUrl));
                logger.info("URL 成功打开 (Desktop.browse): {}", finalUrl);
                return true;
            } catch (IOException | URISyntaxException e) {
                logger.error("打开 URL 失败 (Desktop.browse): {}", e.getMessage(), e);
                return false;
            }
        }
    }

    /**
     * 将文本内容粘贴到当前活动窗口（用于内容生成）。
     * @param text 要粘贴的文本
     */
    public static void pasteText(String text) {
        try {
            StringSelection stringSelection = new StringSelection(text);
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(stringSelection, null);

            Robot robot = new Robot();
            robot.delay(200);

            boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac");

            if (isMac) {
                robot.keyPress(KeyEvent.VK_META);
                robot.keyPress(KeyEvent.VK_V);
                robot.keyRelease(KeyEvent.VK_V);
                robot.keyRelease(KeyEvent.VK_META);
            } else {
                robot.keyPress(KeyEvent.VK_CONTROL);
                robot.keyPress(KeyEvent.VK_V);
                robot.keyRelease(KeyEvent.VK_V);
                robot.keyRelease(KeyEvent.VK_CONTROL);
            }
            logger.info("成功模拟粘贴文本。");
        } catch (Exception e) {
            logger.error("粘贴文本失败: {}", e.getMessage(), e);
        }
    }
    /**
     * 【【【 新增方法：发送 CTRL+P 】】】
     * 模拟发送 CTRL+P 快捷键 (用于网易云音乐播放)。
     */
    public static void sendCtrlP() {
        try {
            Robot robot = new Robot();
            robot.delay(100);

            // 仅在 Windows 上实现 CTRL+P
            boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac");
            if (isMac) {
                logger.warn("【快捷键】Mac 上的播放快捷键 (CTRL+P) 可能无效，请自行调整。");
                robot.keyPress(KeyEvent.VK_CONTROL);
                robot.keyPress(KeyEvent.VK_P);
                robot.keyRelease(KeyEvent.VK_P);
                robot.keyRelease(KeyEvent.VK_CONTROL);
            } else {
                // Windows: CTRL + P
                robot.keyPress(KeyEvent.VK_CONTROL);
                robot.keyPress(KeyEvent.VK_P);
                robot.keyRelease(KeyEvent.VK_P);
                robot.keyRelease(KeyEvent.VK_CONTROL);
            }
            logger.info("成功模拟发送快捷键 CTRL+P。");
        } catch (Exception e) {
            logger.error("模拟快捷键 CTRL+P 失败: {}", e.getMessage(), e);
        }
    }
}